package com.ujcms.commons.security;

import com.nimbusds.jose.KeyLengthException;
import com.ujcms.cms.core.service.ConfigService;
import com.ujcms.cms.core.service.OrgService;
import com.ujcms.cms.core.service.SiteService;
import com.ujcms.cms.core.service.UserService;
import com.ujcms.cms.core.support.Props;
import com.ujcms.cms.core.web.support.*;
import com.ujcms.commons.freemarker.OsTemplateLoader;
import com.ujcms.commons.security.jwt.HmacSm3JwsSigner;
import com.ujcms.commons.security.jwt.HmacSm3JwsVerifier;
import com.ujcms.commons.security.jwt.JwtProperties;
import com.ujcms.commons.web.DirectoryRedirectInterceptor;
import com.ujcms.commons.web.TimerInterceptor;
import jakarta.servlet.ServletContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.util.Properties;

import static com.ujcms.cms.core.support.UrlConstants.*;

/**
 * @author 10100
 */
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
    private final UserService userService;
    private final SiteService siteService;
    private final OrgService orgService;
    private final ConfigService configService;
    private final Props props;
    private final ResourceLoader resourceLoader;
    private final ServletContext servletContext;

    public WebConfigurer(UserService userService, SiteService siteService, OrgService orgService,
                         ConfigService configService, Props props,
                         ResourceLoader resourceLoader, ServletContext servletContext) {
        this.userService = userService;
        this.siteService = siteService;
        this.orgService = orgService;
        this.configService = configService;
        this.props = props;
        this.resourceLoader = resourceLoader;
        this.servletContext = servletContext;
    }

    private static final String ERROR_PATH_PATTERN = "/error/**";
    private static final String EXTENSION_PATH_PATTERN = "/**/*.*";

    /**
     * 异常处理
     */
    @Bean
    public ExceptionResolver exceptionResolver() {
        return new ExceptionResolver(configService);
    }

    /**
     * 计时器拦截器
     */
    @Bean
    public TimerInterceptor timerInterceptor() {
        return new TimerInterceptor();
    }

    /**
     * JWT 配置
     */
    @Bean
    public JwtProperties jwtProperties() {
        return new JwtProperties();
    }

    /**
     * JWT 签名。使用国密 HmacSM3。
     */
    @Bean
    public HmacSm3JwsSigner hmacSm3JwsSigner() throws KeyLengthException {
        return new HmacSm3JwsSigner(jwtProperties().getSecret());
    }

    /**
     * JWT 验证。使用国密 HmacSM3。
     */
    @Bean
    public HmacSm3JwsVerifier hmacSm3JwsVerifier() throws KeyLengthException {
        return new HmacSm3JwsVerifier(jwtProperties().getSecret());
    }

    /**
     * 后台拦截器
     */
    @Bean
    public BackendInterceptor backendInterceptor() {
        return new BackendInterceptor(userService, siteService, orgService, props);
    }

    /**
     * 前台拦截器
     */
    @Bean
    public FrontendInterceptor frontendInterceptor() {
        return new FrontendInterceptor(userService, siteService, configService);
    }

    /**
     * 前台拦截器
     */
    @Bean
    public FrontendApiInterceptor frontendApiInterceptor() {
        return new FrontendApiInterceptor(userService);
    }

    /**
     * URL重写拦截器
     */
    @Bean
    public UrlRedirectInterceptor urlRedirectInterceptor() {
        return new UrlRedirectInterceptor(configService);
    }

    /**
     * 目录重定向拦截器。支持访问
     */
    @Bean
    public DirectoryRedirectInterceptor directoryRedirectInterceptor() {
        return new DirectoryRedirectInterceptor(resourceLoader, props.isFileToDir(), props.isDirToFile());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 错误页面和带点的文件请求 jquery.js bootstrap.min.css 都不经过拦截器
        registry.addInterceptor(timerInterceptor()).excludePathPatterns(ERROR_PATH_PATTERN, EXTENSION_PATH_PATTERN);
        // 后台拦截器
        registry.addInterceptor(backendInterceptor()).addPathPatterns(BACKEND_API + "/**");
        // 目录重定向拦截器
        if (props.isFileToDir() || props.isDirToFile()) {
            registry.addInterceptor(directoryRedirectInterceptor())
                    .excludePathPatterns(API + "/**", FRONTEND_API + "/**", ERROR_PATH_PATTERN, EXTENSION_PATH_PATTERN);
        }
        // URL重写拦截器
        registry.addInterceptor(urlRedirectInterceptor())
                .excludePathPatterns(API + "/**", FRONTEND_API + "/**", ERROR_PATH_PATTERN, EXTENSION_PATH_PATTERN);
        // 前台拦截器
        registry.addInterceptor(frontendInterceptor())
                .excludePathPatterns(API + "/**", FRONTEND_API + "/**", ERROR_PATH_PATTERN, EXTENSION_PATH_PATTERN);
        // 前台API拦截器
        registry.addInterceptor(frontendApiInterceptor())
                .addPathPatterns(API + "/**", FRONTEND_API + "/**")
                .excludePathPatterns(BACKEND_API + "/**");
    }

    @Override
    public void addCorsMappings(@NonNull CorsRegistry registry) {
        // 允许 api 跨域
        //registry.addMapping(API + "/**").allowedOrigins("*").allowedMethods("GET", "POST").allowCredentials(true)
    }

    @Override
    public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
        // 在开发环境下，上传的图片无法立即访问，需要等待1至5秒不等。
        // 可能是因为上传后，开发工具需要同步到实际tomcat的运行路径下。
        // 需要将上传文件定位到真实路径。可以通过设置 spring.web.resources.static-locations 实现。
        // 也可考虑判断 profile 进行处理。
        // List<String> profiles = Arrays.asList(applicationContext.getEnvironment().getActiveProfiles())
        String uploadsLocation = props.getUploadsLocation();
        if (StringUtils.isNotBlank(uploadsLocation)) {
            registry.addResourceHandler(uploadsLocation + "/**")
                    .addResourceLocations("file:" + servletContext.getRealPath(uploadsLocation + "/"));
        }
    }

    @Bean
    @ConditionalOnProperty(prefix = "ujcms", name = "template-loader-url")
    public FreeMarkerConfigurer freeMarkerConfigurer(
            FreeMarkerProperties properties,
            @Value("${ujcms.template-loader-url}") String templateLoaderUrl) {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setPreTemplateLoaders(new OsTemplateLoader(templateLoaderUrl));
        // 使用 URL 加载模板，需关闭此项
        configurer.setPreferFileSystemAccess(false);
        configurer.setDefaultEncoding(properties.getCharsetName());
        Properties settings = new Properties();
        settings.put("recognize_standard_file_extensions", "true");
        settings.putAll(properties.getSettings());
        configurer.setFreemarkerSettings(settings);
        return configurer;
    }
}